﻿namespace Cellfish.Toolbox.Facts.Tasks
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    using Cellfish.Test;
    using Cellfish.Toolbox.Tasks;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class WithCancellationExtensionsFacts
    {
        private CancellationTokenSource cancellationSource = new CancellationTokenSource();

        [TestMethod]
        public void Given_a_null_task_When_adding_cancellation_Then_ArgumentNullException_is_thrown()
        {
            Task task1 = null;
            Task<int> task2 = null;
            Ensure.Throws<ArgumentNullException>(() => task1.WithCancellation(this.cancellationSource.Token), "Failed for Task");
            Ensure.Throws<ArgumentNullException>(() => task2.WithCancellation(this.cancellationSource.Token), "Failed for Task<T>");
        }

        [TestMethod]
        public void Given_a_completed_task_and_cancelled_token_When_adding_cacellation_Then_task_result_is_returned()
        {
            Task<int> task = Task.FromResult(42);
            var cancellationToken = new CancellationTokenSource();
            cancellationToken.Cancel();
            Assert.AreEqual(42, task.WithCancellation(cancellationToken.Token).Result, "Failed for Task<T>");
            Assert.IsTrue(((Task)task).WithCancellation(cancellationToken.Token).Wait(TimeSpan.FromSeconds(1)), "Failed for Task");
        }

        [TestMethod]
        public void Given_a_task_When_adding_cancellation_and_task_is_itself_cancelled_Then_task_is_cancelled()
        {
            var taskCancellation = new CancellationTokenSource();
            var task = Task.Delay(TimeSpan.FromHours(1), taskCancellation.Token);
            var cancellationToken = new CancellationTokenSource();
            var taskWithCancellation = task.WithCancellation(cancellationToken.Token);
            taskCancellation.Cancel();
            Ensure.TaskFails<TaskCanceledException>(taskWithCancellation);
        }

        [TestMethod]
        public void Given_a_task_When_adding_cancellation_and_task_is_failing_Then_task_is_faulted()
        {
            var continueTask = new ManualResetEvent(false);
            var task = Task.Factory.StartNew(
                () =>
                    {
                        continueTask.WaitOne();
                        throw new Exception();
                    });
            var cancellationToken = new CancellationTokenSource();
            var taskWithCancellation = task.WithCancellation(cancellationToken.Token);
            continueTask.Set();
            Ensure.TaskFails<Exception>(taskWithCancellation);
        }

        [TestMethod]
        public void Given_a_task_When_adding_cancellation_and_task_completes_Then_task_result_is_returned()
        {
            var continueTask = new ManualResetEvent(false);
            var task = Task.Factory.StartNew(
                () =>
                {
                    continueTask.WaitOne();
                    return 42;
                });
            var cancellationToken = new CancellationTokenSource();
            var taskWithCancellation = task.WithCancellation(cancellationToken.Token);
            continueTask.Set();
            Assert.AreEqual(42, taskWithCancellation.Result);
        }

        [TestMethod]
        public void Given_a_task_When_adding_cancellation_and_cancellation_is_requested_Then_task_is_cancelled()
        {
            var continueTask = new ManualResetEvent(false);
            var task = Task.Factory.StartNew(() => continueTask.WaitOne());
            var cancellationToken = new CancellationTokenSource();
            var taskWithCancellation = task.WithCancellation(cancellationToken.Token);
            cancellationToken.Cancel();
            continueTask.Set();
            Ensure.TaskFails<TaskCanceledException>(taskWithCancellation);
        }
    }
}
